#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <libv4l2.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <assert.h>
#include <gtk/gtk.h>
#include "capture.h"
#include "debug.h"
#include <libv4lconvert.h>
#include <arm_neon.h>
#include "NeonFunctions.h"
#include "util.h"
#include "v4lconvert.h"
#include "ImageFunctions.h"

#define PERCENT 75
#define BYTE_MEIO YUV422_SIZE_BYTES/2
int first_offset = (BYTE_MEIO - ((YUV422_SIZE_BYTES*PERCENT)/100)/2);
int end_offset = (BYTE_MEIO + ((YUV422_SIZE_BYTES*PERCENT)/100)/2);
int part_size;

float y[256],u_green[256],u_blue[256],v_red[256],v_green[256];
float ***yuv;
FILE *saida2;

static void pixbuf_destroy(guchar *pixels, gpointer data);
static int xioctl(int fh, int request, void *arg) ;
static void errno_exit(const char * s);

#define CLEAR(x) memset(&(x), 0, sizeof(x))
static unsigned char *novo;
static int fd = -1;
struct buffer * buffers = NULL; 
static unsigned int n_buffers = 0;
struct buffer * rgb=NULL;
struct buffer *anterior=NULL;
int flag=0; //boleano para fazer a conversão para RGB24 apenas a primeira vez
int count=0;
int i,pixel_count=0;

static void errno_exit(const char * s) {
    fprintf(stderr, "%s error %d, %s\n",
            s, errno, strerror(errno));
	DEBUG_LINE();
    exit(EXIT_FAILURE);
}

/*
 *Problemas...
 * ioctl() é uma chamada de sistema para o específico do 
 * dispositivo de entrada / saída 
 * de operações e outras operações que não podem 
 * ser expressas pelo sistema de chamadas regulares.
 * */
static int xioctl(int fh, int request, void *arg) {
    int r;
	
	DEBUG_LINE();
    
    do {
		
    	DEBUG_LINE();
        r = v4l2_ioctl(fh, request, arg);
       	DEBUG_LINE();
    } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
	
	DEBUG_LINE();
	
    if (r == -1) {
	
        fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    //printf("DEU");
    DEBUG_LINE();
    return 1; /*perigo*/
}
/*inicia o dispositivo variavel fd
DPO - a função ficou definida como void
*/
void open_device(void) {
    struct stat st;
	DEBUG_LINE();
	
    if (-1 == stat(dev_name, &st)) {
        fprintf(stderr, "Cannot identify '%s': %d, %s\n",
                dev_name, errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    DEBUG_LINE();
    if (!S_ISCHR(st.st_mode)) {
        fprintf(stderr, "%s is no device\n", dev_name);
        exit(EXIT_FAILURE);
    }
	DEBUG_LINE();
	//fd: descritor do arquivo
    fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
    printf("fd: %d\n",fd);
	DEBUG_LINE();
    if (-1 == fd) {
        fprintf(stderr, "Cannot open '%s': %d, %s\n",
                dev_name, errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    DEBUG_LINE();
//    novo = (unsigned char*)malloc(sizeof(char)*640*480*4);/*arrumar*/
//    if(novo==NULL){
//    	printf("Não foi possível alocar memória");
//    	DEBUG_LINE();
//    	exit(1);/*tratar erro*/
//    }
    DEBUG_LINE();
    
}


void init_device(void) {
    struct v4l2_format fmt;
    unsigned int min;
	DEBUG_LINE();

    CLEAR(fmt);

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    //fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; //era RGB24 Fernando
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
 	DEBUG_LINE();
    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
        errno_exit("VIDIOC_S_FMT");
	DEBUG_LINE();
    init_mmap();
	DEBUG_LINE();

}


void init_mmap(void) {
    struct v4l2_requestbuffers req;
	struct v4l2_buffer buf; //DPO 12/06/13
    CLEAR(req);
	
	DEBUG_LINE();

    req.count = 2;/*= 4; /*=2?*///The number of buffers requested or granted.
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    //req.reserved: A place holder for future extensions. This array should be zeroed by applications.
	//VIDIOC_REQBUFS: Initiate Memory Mapping or User Pointer I/O
    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
        if (EINVAL == errno) {
            fprintf(stderr, "%s does not support "
                    "memory mapping\n", dev_name);
            exit(EXIT_FAILURE);
        } else {
            errno_exit("VIDIOC_REQBUFS");
        }
    }
	DEBUG_LINE();
	//aloca req.count tamanho para a struct buffers
    buffers = calloc(req.count, sizeof (*buffers));
	rgb = calloc(req.count, sizeof (*rgb));
	anterior = calloc(req.count, sizeof (*anterior));
    
	
    
    if (!buffers) {
        fprintf(stderr, "Out of memory\n");
        exit(EXIT_FAILURE);
    }
	DEBUG_LINE();
    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
        CLEAR(buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;
		//buf.bytesused = 2764800;
        if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
            errno_exit("VIDIOC_QUERYBUF");
		
        buffers[n_buffers].length = buf.length;
        rgb[n_buffers].length=buf.length;
        buffers[n_buffers].start =
                v4l2_mmap(NULL /* start anywhere */,
                buf.length,
                PROT_READ | PROT_WRITE /* required */,
                MAP_SHARED /* recommended */,
                fd, buf.m.offset);
                
		//rgb[n_buffers].start=malloc(buffers[n_buffers].length*sizeof(unsigned char)*3);
		//para 1280x720 o tamanho e 1280*720*3
		rgb[n_buffers].start=calloc(RGB24_SIZE_BYTES ,sizeof(unsigned char));
		printf("RGB24_SIZE_BYTES %d\n",RGB24_SIZE_BYTES);
		
		//
		anterior[n_buffers].start=malloc(buffers[n_buffers].length*sizeof(unsigned char));
		printf("ANTERIOR SIZE %d\n",buffers[n_buffers].length);
		
		//anterior[n_buffers].start=malloc(16777216*sizeof(unsigned char));
		anterior[n_buffers].length=buf.length;
		
		if (!rgb[n_buffers].start || !anterior[n_buffers].start) {
        		fprintf(stderr, "Out of memory\n");
       			 exit(EXIT_FAILURE);
		}
		/*Note device memory accesses (e. g. the memory on a graphics card with video capturing hardware) may incur a performance 
		penalty compared to main memory 		accesses, or reads may be significantly slower than writes or vice versa. 
		Other I/O methods may be more efficient in this case.*/
		
        if (MAP_FAILED == buffers[n_buffers].start)
            errno_exit("mmap");
    }
	DEBUG_LINE();
   /* int i;
    for(i = 0;i<256;i++){
		y[i] = (i-16)*1.164;
		u_green[i] = (i-128)*0.391;
		u_blue[i] = (i-128)*2.018;
		v_red[i] = (i-128)*1.596;
		v_green[i] = (i-128)*0.813;
	}
	
	MALLOC_YUV_TABLE(yuv);*/
	//seta aqui o tamanho da parte
	part_size = end_offset - first_offset;
}

void start_capturing(void) {
    unsigned int i;

    enum v4l2_buf_type type;
    //struct v4l2_buffer buf; //coloquei antes do for...DPO 28/07/13
	DEBUG_LINE();
	DEBUG_PRINT("N_BUFFERS : %d\n", n_buffers);
    for (i = 0; i < n_buffers; ++i) {
        
		struct v4l2_buffer buf;
        CLEAR(buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//buf.type é o tipo do buffer
        buf.memory = V4L2_MEMORY_MMAP;//deve ser setado pela aplicação e/ou drivers conforme o metodo de I/O
        buf.index = i; //numero do buffer colocado pela aplicação
	
        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
            errno_exit("VIDIOC_QBUF");
    }
	DEBUG_LINE();
	
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
        errno_exit("VIDIOC_STREAMON");
	 
	
	
}

void uninit_device (void)
{
        unsigned int i;

	
		for (i = 0; i < n_buffers; ++i)
			if (-1 == munmap (buffers[i].start, buffers[i].length))
				errno_exit ("munmap");
		
	free (buffers);
}


void  stop_capturing (void)
{
        enum v4l2_buf_type type;


		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

		if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
			errno_exit ("VIDIOC_STREAMOFF");

}

void pixbuf_destroy(guchar *pixels, gpointer data){

	//free(pixels);
	
}




int read_frame(void (*process_image)()){
	
	struct v4l2_buffer buf;
		
	CLEAR (buf);
	
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	
	buf.memory = V4L2_MEMORY_MMAP;
	
	//VIDIOC_DQBUF Exchange a buffer with the driver
	if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
		
		switch (errno) {
			case EAGAIN:
				printf("EAGAIN\n");
				return 0;
			case EIO:
			
						/* Could ignore EIO, see spec. */
						/* fall through */
			default:
				errno_exit ("VIDIOC_DQBUF");
		}
	}
	
	
	//assert ajuda a encontrar bugs no programa
	assert (buf.index < n_buffers);
	/**
	 * A diferenca de imagens e feita com o formato YUV422. Dessa forma, evita-se uma conversao desnecessaria 
	 * de YUV422 para RGB24 e o processamento da imagem desnecessaria
	 * */
    if(flag==0){//entra somente uma vez
		v4lconvert_yuyv_to_rgb24_optimized_NEON_final(buffers[buf.index].start,rgb[buf.index].start,1280,720);
		memcpy(anterior[buf.index].start,buffers[buf.index].start,sizeof(unsigned char)*buffers[buf.index].length);
		flag=1;
	}
	pixel_count = image_diff_section(buffers[buf.index].start,anterior[buf.index].start, first_offset, end_offset);
	//printf("END_OFFSET : %d\n",first_offset);
	//pixel_count = image_diff_section_NEON(buffers[buf.index].start,anterior[buf.index].start,
					//first_offset, end_offset, WIDTH,HEIGHT);
	if((double)(pixel_count)/(double)(part_size)>0){
		v4lconvert_yuyv_to_rgb24_optimized_NEON_final(buffers[buf.index].start,rgb[buf.index].start,1280,720);
		memcpy(anterior[buf.index].start,buffers[buf.index].start,sizeof(unsigned char)*buffers[buf.index].length);				
		process_image ((unsigned char *)rgb[buf.index].start);
	}
	
    if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
		errno_exit ("VIDIOC_QBUF");
	return 1;
}


//time handler chama essa função..
void call_process_image(void (*process_image)(unsigned char*)) {
    //struct v4l2_buffer buf; //nao esta sendo utilizada nessa funcao DPO
    
    unsigned int i;
	
    //CLEAR(buf);
    
	DEBUG_LINE();
	//printf("inicio do call process image\n");
	while(1){
		
		fd_set fds;
		struct timeval tv;
		int r;
		//do{
		FD_ZERO (&fds);
		FD_SET (fd, &fds);
		/* Timeout. */
		tv.tv_sec = 2;
		tv.tv_usec = 0;

		r = select (fd + 1, &fds, NULL, NULL, &tv);
		
		//}while ((r == -1 && (errno = EINTR)));
		if (-1 == r) {
			printf("while\n");
			if (EINTR == errno)
				continue;
		    errno_exit ("select");
		}
		if (0 == r) {
			fprintf (stderr, "select timeout\n");
			exit (EXIT_FAILURE);
		}
		
		if(read_frame(process_image)==0){	
			continue;
		}
		else{
			break;
		}
	}
	//printf("DEU BREAK\n");
	//printf("TIME");
}




void close_device                    (void)
{
        if (-1 == close (fd))
	        errno_exit ("close");

        fd = -1;
}

